package hoodoos.rule.script.java;


import hoodoos.rule.api.Rule;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class JarLoader {

  private static final Logger logger = LoggerFactory.getLogger(JarLoader.class);

  private final Map<String, Rule> ruleMap = new HashMap<>();
  private final Set<String> hadLoadedJars = new HashSet<>();

  public Rule getRule(String rulerName) {
    return ruleMap.get(rulerName);
  }

  public void load(String jarName, String jarPath, ClassLoader parentClassLoader) {
    try {
      if (hadLoadedJars.contains(jarName)) {
        return;
      }

      URL url = new URL("file:" + jarPath);

      PureJavaClassLoader classLoader = new PureJavaClassLoader(new URL[]{url}, parentClassLoader);

      hadLoadedJars.add(jarName);

      List<String> classNameList = PackageUtil.getClassName(jarPath, "", classLoader, true);
      for (String className : classNameList) {
        logger.debug("loadClass: {}", className);
        scan(className, classLoader);
      }
    }
    catch (MalformedURLException e) {
      throw new RuntimeException(e);
    }
  }

  private void scan(String className, ClassLoader classLoader) {
    try {
      Class clazz = classLoader.loadClass(className);
      PureRule ruleAnnotation = (PureRule) clazz.getDeclaredAnnotation(PureRule.class);
      if (ruleAnnotation == null) {
        return;
      }

      boolean canLoad = (clazz.getModifiers() & Modifier.PUBLIC) > 0
          && (!clazz.isMemberClass() || (clazz.getModifiers() & Modifier.STATIC) > 0)
          && Rule.class.isAssignableFrom(clazz);

      if (!canLoad) {
        logger.error("不能通过类加载器加载 Rule: {}", className);
        throw new RuntimeException("不能通过类加载器加载 Rule: " + className);
      }

      String ruleName = ruleAnnotation.value();

      Rule rule = null;
      try {
        Constructor constructor = ReflectUtil.getConstructor(clazz, String.class);
        rule = (Rule) constructor.newInstance(ruleName);
      }
      catch (NoSuchMethodException e) {
        logger.warn("没有找到参数为 String 构造函数: {}，将尝试找无参构造器", className);
      }

      if (rule == null) {
        try {
          Constructor constructor = ReflectUtil.getConstructor(clazz);
          rule = (Rule) constructor.newInstance();
        }
        catch (NoSuchMethodException e) {
          logger.error("没有找到合适的构造函数: {}", className);
          throw new RuntimeException("没有找到无参构造函数: " + className);
        }
      }

      rule.init();
      ruleMap.put(ruleName, rule);
    }
    catch (Exception e) {
      logger.error("Jar scan failed", e);
    }
  }
}
